package cn.yjh;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.security.ProtectionDomain;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.Properties;
import java.util.Scanner;
import java.util.Set;




import com.sun.tools.attach.VirtualMachine;


import cn.yjh.config.ConfigurationManager;
import cn.yjh.constant.CommandEnum;
import cn.yjh.utils.StringUtils;
import sun.jvmstat.monitor.MonitoredHost;
import sun.jvmstat.monitor.MonitoredVm;
import sun.jvmstat.monitor.MonitoredVmUtil;
import sun.jvmstat.monitor.VmIdentifier;
import sun.misc.URLClassPath;


/**
 * @DESC:
 * @AUTHOR: YouJinhua
 * @DATE: 2021-11-18 19:38:13
 * @VERSION: 1.0
 */
public class MonitorBootStrap {

    public static final String AGENT_PATH = "agent_path";

    public static void premain(String args, Instrumentation inst) throws Exception {

        String agent_path = initPath();

        System.setProperty(AGENT_PATH,agent_path);

        // 打印启动 LOGO
        printBanner();

        // 获取当前运行类所在的目录
        //initAgentRootPath();

        // 加载配置文件
        loadConfiguration(args);

        // 加载依赖 jar 包
        loadOptionalDependency(System.getProperty("agent_path"));

        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        Class<?> clazz = loader.loadClass("cn.yjh.DefaultMonitorApplication");
        Constructor<?> constructor = clazz.getDeclaredConstructor();
        constructor.setAccessible(true);
        Object application = constructor.newInstance(null);
        Class<?> parentClazz = loader.loadClass("cn.yjh.AbstractMonitorApplication");
        Method startApplication = parentClazz.getDeclaredMethod("startApplication",String.class);
        startApplication.setAccessible(true);
        startApplication.invoke(application,args);

        inst.addTransformer(new ClassFileTransformer() {

            @Override
            public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
                return new byte[0];
            }
        });
    }


    public static void agentmain(String args, Instrumentation inst) throws Exception{

        // 打印启动 LOGO
        printBanner();

        // 获取当前运行类所在的目录
        //initAgentRootPath();

        // 加载配置文件
        loadConfiguration(args);

        // 加载依赖 jar 包
        loadOptionalDependency(System.getProperty("agent_path"));

        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        Class<?> clazz = loader.loadClass("cn.yjh.DefaultMonitorApplication");
        Constructor<?> constructor = clazz.getDeclaredConstructor();
        constructor.setAccessible(true);
        Object application = constructor.newInstance(null);
        Class<?> parentClazz = loader.loadClass("cn.yjh.AbstractMonitorApplication");
        Method startApplication = parentClazz.getDeclaredMethod("startApplication",String.class);
        startApplication.setAccessible(true);
        startApplication.invoke(application,args);


        inst.addTransformer(new ClassFileTransformer() {

            @Override
            public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
                return new byte[0];
            }
        });
    }

    public static void main(String[] args) throws Exception {
        String path = initPath();

        // 加这个判断主要是为了直接运行
        if(path.endsWith("target")){
            path += "/monitor-bootstrap/monitor-bootstrap";

        }
        try {
            List<String> PIDS = new ArrayList();
            // 获取监控主机
            MonitoredHost local = MonitoredHost.getMonitoredHost("localhost");
            // 取得所有在活动的虚拟机集合
            Set<Integer> vmList = new HashSet(local.activeVms());

            for(Integer process : vmList) {

                MonitoredVm vm = local.getMonitoredVm(new VmIdentifier("//" + process));
                // 获取类名
                String processname = MonitoredVmUtil.mainClass(vm, true);

                outPrintln("| "+process + " | " + processname);

                PIDS.add(String.valueOf(process));
            }

            Scanner scanner = new Scanner(System.in);
            outPrintln("Please Select One PID");
            while (true){
                String PID = scanner.nextLine();
                if(PIDS.contains(PID)){
                    VirtualMachine vm = VirtualMachine.attach(PID);
                    vm.loadAgent(path+"/monitor-bootstrap.jar","agent_path="+path.replaceAll("\\\\", "/"));
                    printBanner();
                    break;
                }else{
                    if("exit".equals(PID)){
                        break;
                    }else{
                        outPrintln("The PID Not In The List, Please Reselect One PID!");
                    }
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    public static String initPath() throws Exception {
        URL url = MonitorBootStrap.class.getProtectionDomain().getCodeSource().getLocation();
        String rootPath = URLDecoder.decode(url.getPath(), "UTF-8");
        if(url.getProtocol().equals("file")){
            File parentFile = new File(rootPath).getParentFile();
            rootPath = parentFile.getPath();
            return rootPath;
        }
        // 获取当前操作系统名称
        String systemType = System.getProperty("os.name").toUpperCase(Locale.ROOT);
        String path = url.getPath();
        if(systemType.indexOf("WINDOWS")!=-1){
            path = path.substring(1,path.lastIndexOf("/"));
        }else{
            path = path.substring(0,path.lastIndexOf("/"));
        }
        return path;
    }


    public static void loadOptionalDependency(String agent_root_path) throws Exception {
        boolean isDefaultLogger = true;
        try{
            Class.forName("org.slf4j.LoggerFactory");
        }catch (ClassNotFoundException e){
            isDefaultLogger = false;
        }
        File file = new File(agent_root_path,"/lib/opt/");
        if(!file.exists()){
            throw new RuntimeException("the optional dependency dir [ "+agent_root_path+"/lib/opt ] not exist !");
        }

        List<URL> urlList = new ArrayList<>();
        for (String fileName: file.list()){
            if(isDefaultLogger && fileName.indexOf("slf4j-api") != -1){
                continue;
            }
            File jarFile = new File(file.getPath() , fileName);
            URL url = jarFile.toURI().toURL();
            urlList.add(url);
        }

        // 创建新的类加载器
        URL[] urLs = new URL[urlList.size()];
        urlList.toArray(urLs);

        ClassLoader appLoader = Thread.currentThread().getContextClassLoader();
        Method addURL = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
        addURL.setAccessible(true);
        for(URL url:urlList){
            addURL.invoke(appLoader,url);
        }

        /*ClassLoader oldeLoader = Thread.currentThread().getContextClassLoader();
        Field ucp = URLClassLoader.class.getDeclaredField("ucp");
        ucp.setAccessible(true);
        URLClassPath urlClassPath = (URLClassPath) ucp.get(oldeLoader);
        for(URL url:urlClassPath.getURLs()){

        }
        URLClassLoader newLoader = new MonitorClassLoader(urLs);
        Thread.currentThread().setContextClassLoader(urlClassLoader);*/

        if(isDefaultLogger){
            outPrintln("use application Logger");
        }else {
            outPrintln("use default Logger: [ logback ]");
        }
    }


    public static ConfigurationManager loadConfiguration(String args) throws Exception {
        ConfigurationManager configurationManager = new ConfigurationManager();
        Properties prop = System.getProperties();
        // 加载命令行参数
        Optional.ofNullable(args).ifPresent(arg->{
            byte[] bytes = args.getBytes(StandardCharsets.UTF_8);
            try (InputStream in = new ByteArrayInputStream(bytes);){
                prop.load(in);
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
        Object configLocation = prop.get(CommandEnum.CONFIG_LOCATION.KEY);
        // 根据命令行参数，优先加载命令行参数中指定的配置文件
        if(StringUtils.isNotEmpty(configLocation)){
            File file = new File(configLocation.toString());
            if(!file.exists()){
                throw new RuntimeException("the conf file [ "+configLocation+" ] not exist !");
            }
            //logger.info("load config from path：{}",configLocation);
            try (InputStream in = new FileInputStream(file);){
                prop.load(in);
                configurationManager.addConfigResource(ConfigurationManager.ACTIVE_CONFIGURATION,prop);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(configurationManager.hasActiceConfiguration()){

        }

        String agent_root_path = Optional.ofNullable(prop.get(AGENT_PATH)).map(Object::toString).orElseThrow(() -> new RuntimeException("the agent_path is null"));
        File file = new File(agent_root_path,"/conf/bootstrap.properties");
        if(!file.exists()){
            throw new RuntimeException("the conf file [ "+agent_root_path+"/conf/bootstrap.properties ] not exist !");
        }
        InputStream in = new FileInputStream(file);
        if(configurationManager.hasActiceConfiguration()){
            Properties properties = new Properties();
            properties.load(in);
            configurationManager.addConfigResource("bootstrapConfiguration",properties);
        }else{
            prop.load(in);
            configurationManager.addConfigResource(ConfigurationManager.ACTIVE_CONFIGURATION,prop);
        }
        return configurationManager;
    }

    public static void outPrintln(String str ){
        DateFormat df=new SimpleDateFormat("[ yyyy-MM-dd:hh:mm:ss ]");
        System.out.println(df.format(new Date())+" "+str);
    }

    public static void printBanner() {
        InputStream in = ClassLoader.getSystemResourceAsStream("LOGO.txt");
        try (InputStreamReader reader = new InputStreamReader(in, "utf-8");
             BufferedReader br = new BufferedReader(reader)) {
            while ((br.read()) != -1) {
                System.out.println("\033[1;36m" + br.readLine());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("\033[0m");
    }

}
